package com.ruoyi.common.core.redis;

import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toList;

@Component
public class CommonRedisService {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${spring.redis.scope}")
    private String redis_scope;

    @Value("${spring.redis.sdk_scope}")
    private String redis_sdk_scope;

    /**
     * 从Redis中取出数据
     * @param key
     * @return
     */

    public Object get(String key) {
        final String keyf = getAppKey(key);
        Object object = null;
        try {
            object = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection)
                        throws DataAccessException {
                    byte[] key = keyf.getBytes();
                    byte[] value = connection.get(key);
                    if (value == null) {
                        return null;
                    }
                    return JSON.parse(value);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("error", e);//默认只打印日志，避免其他业务方法忘记捕获运行时异常时不走数据库
        }
        return object;
    }

    /**
     * 将数据放入Redis中
     * @param key
     * @param value
     * @param expire 单位秒
     */
    public void put(String key, Object value, Long expire) {
        final String fKey = getAppKey(key);
        final Object fValue = value;
        final Long fe = expire;
        redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] keyb = fKey.getBytes();
                byte[] valueb = JSON.toJSONBytes(fValue);
                if(fe != null && fe > 0){
                    connection.setEx(keyb, fe, valueb);
                }
                else{
                    connection.set(keyb, valueb);
                }
                return 1L;
            }
        });
    }

//    public Long incr(String key) {
//        final String fKey = getAppKey(key);
//        return redisTemplate.execute(new RedisCallback<Long>() {
//            public Long doInRedis(RedisConnection connection)
//                    throws DataAccessException {
//                byte[] keyb = fKey.getBytes();
//                connection.incr(keyb);
//                return 1L;
//            }
//        });
//    }

    @Async
    public void asyncPut(String key, Object value, Long expire) {
        put(key, value, expire);
    }

    /**
     * 将数据放入redis，如果此key不存在的话；否则失败
     * @param key
     * @param value
     */
    public void putNX(String key, Object value) {
        final String fKey = getAppKey(key);
        final Object fValue = value;
        redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] keyb = fKey.getBytes();
                byte[] valueb = JSON.toJSONBytes(fValue);

                connection.setNX(keyb, valueb);
                return 1L;
            }
        });
    }

    public boolean setNX(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
                    connection.close();
                    return success;
                }
            });
        } catch (Exception e) {
            logger.error("setNX redis error, key : {}", key);
        }
        return obj != null ? (Boolean) obj : false;
    }

    /**
     * 多服务器集群，使用下面的方法，代替System.currentTimeMillis()，获取redis时间，避免多服务的时间不一致问题！！！
     *
     * @return
     */
    public long currtTimeForRedis() {
        Object obj = redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                return redisConnection.time();
            }
        });
        return obj == null ? -1 : Long.parseLong(obj.toString());
    }

    public boolean expire(final String key, final Long expireTime) {
        Object obj = null;
        try {
            obj =  redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    boolean bool = connection.expire(serializer.serialize(getAppKey(key)), expireTime);
                    connection.close();
                    return bool;
                }
            });
            return (Boolean)obj;
        } catch (Exception e) {
            logger.error("expire redis error, key : {}", key);
        }
        return false;
    }

    /**
     * 将key绑定的value从redis中删除
     * @param key
     */

    public void remove(String key){
        final String fKey = getAppKey(key);
        try {
            redisTemplate.execute(new RedisCallback<Long>() {
                public Long doInRedis(RedisConnection connection)
                        throws DataAccessException {
                    byte[] keyb = fKey.getBytes();
                    connection.del(keyb);
                    return 1L;
                }
            });
        } catch (Exception e) {
            logger.error("error", e);//默认只打印日志，避免其他业务方法忘记捕获运行时异常时不走数据库
        }
    }
    public boolean existObjectKey(String key){
        return redisTemplate.hasKey(getAppKey(key));
    }

    public Object existsKey(String key) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.exists(getAppKey(key).getBytes()));
    }
    /**
     * 往listName列表右边插入值value
     * @param key
     * @param value
     */
    public void setList(String key,String value){
        stringRedisTemplate.opsForList().rightPush(getAppKey(key), value);
    }
    /**
     * 列表listName中的左边第一个对象出栈
     * @param key
     * @return
     */
    public String getList(String key){
        return stringRedisTemplate.opsForList().leftPop(getAppKey(key));
    }
    public Long getListLength(String key){
        return stringRedisTemplate.opsForList().size(getAppKey(key));
    }
    public void expireListKey(String key,Long timeout,TimeUnit unit){
        stringRedisTemplate.expire(getAppKey(key), timeout, unit);

    }
    public long getListKeyExpire(String key){
        return stringRedisTemplate.getExpire(getAppKey(key));
    }


    public boolean existListKey(String key){
        return stringRedisTemplate.hasKey(getAppKey(key));
    }
    public void removeListKey(String key){
        stringRedisTemplate.delete(getAppKey(key));

    }
    private String getAppKey(String key){
        return redis_scope+key;
    }

    private String getAppSdkKey(String key){
        return redis_sdk_scope + key;
    }

//    public Set<String> keys(String pattern){
//        final String fKey = getAppKey(pattern);
//        try {
//            Set<byte[]> resultSet = redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
//                public Set<byte[]> doInRedis(RedisConnection connection)
//                        throws DataAccessException {
//                    byte[] keyb = fKey.getBytes();
//                    return connection.keys(keyb);
//                }
//            });
//            HashSet<String> strSet = new HashSet<String>();
//            for (byte[] bs : resultSet) {
//                strSet.add(new String(bs).substring(redis_scope.length()));
//            }
//            return strSet;
//
//
//        } catch (Exception e) {
//            logger.error("error", e);//默认只打印日志，避免其他业务方法忘记捕获运行时异常时不走数据库
//        }
//        return null;
//    }

    public void removeKeys(String pattern){
        final String fKey = getAppKey(pattern);
        try {
            redisTemplate.execute(new RedisCallback<Void>() {
                public Void doInRedis(RedisConnection connection)
                        throws DataAccessException {
                    byte[] keyb = fKey.getBytes();
                    Set<byte[]> keys = connection.keys(keyb);
                    for (byte[] bs : keys) {
                        connection.del(bs);
                    }
                    return null;
                }
            });

        } catch (Exception e) {
            logger.error("error", e);//默认只打印日志，避免其他业务方法忘记捕获运行时异常时不走数据库
        }
    }


    /**
     * 镜像层_加载
     * @param key
     * @param time
     * @param parameter 搜索参数
     * @param rslt	爬来的数据
     */
    @SuppressWarnings("unchecked")
    public void mirroringCache(String key,Long time,String parameter,Map<String,Object> rslt){
        key = getAppKey(key);
        Map<String,Object> map = null;
        if(this.get(key)!=null){
            map = (Map<String, Object>) this.get(key);
            map.put(parameter, rslt);
            this.put(key, map, time);
        }else{
            map = new HashMap<String, Object>();
            map.put(parameter, rslt);
            this.put(key, map, time);
        }
    }

    /**
     * 镜像层_读取
     * @param key
     * @param parameter 搜索参数
     * @return
     */
    @SuppressWarnings("unchecked")
    public Object mirroringCache(String key,String parameter){
        key = getAppKey(key);
        Map<String, Object> rstl = new HashMap<String, Object>();
        if(this.get(key)==null || ((Map<String, Object>) this.get(key)).get(parameter)==null){
            rstl.put("desc", "fail");
            rstl.put("status", "500");
            return rstl;
        }else{
            return ((Map<String, Object>) this.get(key)).get(parameter);
        }
    }

    /**
     * 镜像层_清除
     * @param keyArray KEY数组
     */
    public void mirroringClear(String[] keyArray){
        for(int i=0;i<keyArray.length;i++){
            this.remove(getAppKey(keyArray[i]));
        }
    }
    /**
     * 往listName列表右边插入值value
     * @param key
     * @param value
     */
    public void setListObject(String key, Object value){
        redisTemplate.execute(
                (RedisConnection conn) -> conn.rPush(getAppKey(key).getBytes(), JSON.toJSONBytes(value)));
    }
    /**
     * 列表listName中的左边第一个对象出栈
     * @param key
     * @return
     */
    public Object getListObject(String key){
        return redisTemplate.execute(
                (RedisConnection conn) -> JSON.parse(conn.lPop(getAppKey(key).getBytes())));
        //return redisTemplate.opsForList().leftPop(getAppKey(key));
    }

    public <T> void setList(String key,List<T> listValue,long timeout,TimeUnit unit){
        key = getAppKey(key);
        if(stringRedisTemplate.hasKey(key)) {
            stringRedisTemplate.delete(key);
        }
        List<T> listVal=(List<T>)listValue;
        for (T obj : listVal) {
            String v= JSON.toJSONString(obj);
            stringRedisTemplate.opsForList().leftPush(key, v);
        }
        stringRedisTemplate.expire(key, timeout, unit);
    }

    public <T> List<T> getList(String key,Class<T> clazz){
        key = getAppKey(key);
        long end=stringRedisTemplate.opsForList().size(key);
        List<String> list=stringRedisTemplate.opsForList().range(key, 0, end);
        List<T> listT=new ArrayList<T>();
        for(String s:list) {
            listT.add(JSON.parseObject(s,clazz));
        }
        return listT;
    }

    /**
     * ZADD
     * @return
     */
    public Object zAdd(String key, double score, Object value) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.zAdd(getAppKey(key).getBytes(), score, JSON.toJSONBytes(value)));
    }

    /**
     * ZREM
     *
     * @return
     */
    public Object zRem(String key, Object value) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.zRem(getAppKey(key).getBytes(), JSON.toJSONBytes(value))>0);
    }

    /**
     * ZCARD
     *
     * @return
     */
    public Object zCard(String key) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.zCard(getAppKey(key).getBytes()));
    }

    public Object zRemRangeByScore(String key, double min, double max) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.zRemRangeByScore(getAppKey(key).getBytes(), min, max));
    }

    public Object zRemRangeByScore(String key, double score) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.zRemRangeByScore(getAppKey(key).getBytes(), score, score));
    }



    /**
     * ZRANGEBYSCORE
     *
     * @return
     */
    @SuppressWarnings("unchecked")
//    public <O> List<O> zRangeByScore(String key, double min, double max, long offset, long count) {
//        Set<byte[]> result = redisTemplate.execute(
//                (RedisConnection conn) -> conn.zRangeByScore(getAppKey(key).getBytes(), min, max, offset, count));
//        return result.stream().map(JSON::parseObject).map(obj -> (O) obj).collect(toList());
//    }

    /**
     * ZREVRANGEBYSCORE
     *
     * @return
     */
//    @SuppressWarnings("unchecked")
//    public <O> List<O> zRevRangeByScore(String key, double min, double max, long offset, long count) {
//        Set<byte[]> result = redisTemplate.execute(
//                (RedisConnection conn) -> conn.zRevRangeByScore(getAppKey(key).getBytes(), min, max, offset, count));
//        return result.stream().map(JSON::parseObject).map(obj -> (O) obj).collect(toList());
//    }

    /**
     * HSET
     *
     * @return
     */
    public Object hSet(String key, String field, Object value) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.hSet(getAppKey(key).getBytes(), field.getBytes(), JSON.toJSONBytes(value)));
    }

    /**
     * HSETNX
     *
     * @return
     */
    public Object hSetNX(String key, String field, Object value) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.hSetNX(getAppKey(key).getBytes(), field.getBytes(), JSON.toJSONBytes(value)));
    }

    /**
     * HGET
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    public <O> Optional<O> hGet(String key, String field) {
        byte[] result = (byte[])redisTemplate.execute(
                (RedisConnection conn) -> conn.hGet(getAppKey(key).getBytes(), field.getBytes()));
        return Optional.ofNullable((O) JSON.parse(result));
    }

    /**
     * HMGET
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    public <O> List<O> hMGet(String key, String... fields) {
        byte[][] bFields = Arrays.stream(fields).map(String::getBytes).toArray(byte[][]::new);
        List<byte[]> result = (List<byte[]>)redisTemplate.execute(
                (RedisConnection conn) -> conn.hMGet(getAppKey(key).getBytes(), bFields));
        return result.stream().map(JSON::parse).map(obj -> (O) obj).collect(toList());
    }

    /**
     * HDEL
     *
     * @return
     */
    public Object hDel(String key, String field) {
        return redisTemplate.execute(
                (RedisConnection conn) -> conn.hDel(getAppKey(key).getBytes(), field.getBytes()) >0);
    }
    /**
     * HKEYS
     * @param key
     * @return
     */
    public Set<String> hKeys(String key){
        return (Set<String>)redisTemplate.execute(new RedisCallback<Set<String>>() {
            @Override
            public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
                Set<byte[]> setResult=connection.hKeys(getAppKey(key).getBytes());
                Set<String> setFinal=new HashSet<String>();
                for(byte[] bytes:setResult){
                    setFinal.add(new String(bytes));
                }
                return setFinal;
            }
        });
    }

    public void sAdd(String key, List<Object> list) {
        list.stream().forEach(d -> {
            sAdd(key, d);
        });
    }

    public void sAdd(String key, Object obj) {
        redisTemplate.execute(
                (RedisConnection conn) -> conn.sAdd(getAppKey(key).getBytes(), JSON.toJSONBytes(obj)));
    }

    /**
     *  @author: bukaa
     *  @Date: 2020-05-19 16:24
     *  @Description: 随机获取元素
     */
//    public <O> List<O> sRandMember(String key, Integer count) {
//        List<byte[]> result = redisTemplate.execute(
//                (RedisConnection conn) -> conn.sRandMember(getAppKey(key).getBytes(), count));
//        return result.stream().map(JSON::parse).map(obj -> (O) obj).collect(toList());
//    }

    /**
     * 从Redis中取出数据
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    public <O> Optional<O> getAsOptional(String key) {
        Object obj = redisTemplate
                .execute((RedisConnection conn) -> JSON.parse(conn.get(getAppKey(key).getBytes())));
        return Optional.ofNullable((O) obj);
    }

    // *************** 登录SDK相关方法 start *************************
    public boolean hSetSdkNX(String key, String field, Object value) {
        return (boolean)redisTemplate.execute(
                (RedisConnection conn) -> conn.hSetNX(getAppSdkKey(key).getBytes(), field.getBytes(), JSON.toJSONBytes(value)));
    }
    public <O> Optional<O> hGetSdk(String key, String field) {
        byte[] result = (byte[])redisTemplate.execute(
                (RedisConnection conn) -> conn.hGet(getAppSdkKey(key).getBytes(), field.getBytes()));
        return Optional.ofNullable((O) JSON.parse(result));
    }
    public boolean hDelSdk(String key, String field) {
        return (boolean)redisTemplate.execute(
                (RedisConnection conn) -> conn.hDel(getAppSdkKey(key).getBytes(), field.getBytes()) >0);
    }

    // *************** 登录SDK相关方法 start *************************
    public <O> Optional<O> hGetSdkOld(String key, String field) {
        byte[] result = (byte[])redisTemplate.execute(
                (RedisConnection conn) -> conn.hGet(getAppSdkKey(key).getBytes(), field.getBytes()));
        return Optional.ofNullable((O) JSON.parse(result));
    }
    public boolean hDelSdkOld(String key, String field) {
        return (boolean)redisTemplate.execute(
                (RedisConnection conn) -> conn.hDel(getAppSdkKey(key).getBytes(), field.getBytes()) >0);
    }


    public Long zSetRemoveRange(String key,long start,long end){
        final String fKey = getAppKey(key);
        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        return zSetOperations.removeRange(fKey,start,end);
    }

    public Set<ZSetOperations.TypedTuple<String>> zSetRangeWithScores(String key,long start,long end){
        final String fKey = getAppKey(key);
        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        return zSetOperations.rangeWithScores(fKey, start, end);
    }

    public double zSetIncrementScore(String key,String value,double delta){
        final String fKey = getAppKey(key);
        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        return zSetOperations.incrementScore(fKey,value,delta);
    }

    public boolean zSetAdd(String key,String value,double score){
        final String fKey = getAppKey(key);
        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        return zSetOperations.add(fKey,value,score);
    }

    public Long zSetRemove(String key, Object... values){
        final String fKey = getAppKey(key);
        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        return zSetOperations.remove(fKey,values);
    }
    public HashOperations<String, String, Object> hashOperations() {
        return stringRedisTemplate.opsForHash();
    }
    public void hsetMap(String key,Map<String,Object> v,long timeout,TimeUnit unit){
        key = getAppKey(key);
        hashOperations().putAll(key, v);
        if(unit != null) {
            stringRedisTemplate.expire(key, timeout, unit);
        }else {
            stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    public Map<String,Object> hgetMap(String key){
        key = getAppKey(key);
        if(stringRedisTemplate.hasKey(key)) {
            Map<String,Object> m=hashOperations().entries(key);
            return m;
        }else {
            return null;
        }
    }

}
